use super::mut_void;
use crate::platform::{htons, inet_ntop, inet_pton, ntohs};
use crate::Error;
use core::fmt;
use core::mem::{self, MaybeUninit};
use core::ptr;
use core::slice;
use core::str;

#[derive(Clone)]
pub struct SocketAddr {
    addr: MaybeUninit<libc::sockaddr_storage>,
}

impl SocketAddr {
    pub const fn uninit() -> Self {
        Self {
            addr: MaybeUninit::uninit(),
        }
    }

    pub fn zeroed(family: i32) -> Self {
        let mut addr = MaybeUninit::<libc::sockaddr_storage>::zeroed();
        unsafe { addr.assume_init_mut() }.ss_family = family as u16;
        Self { addr }
    }

    pub fn family(&self) -> i32 {
        unsafe { self.addr.assume_init_ref() }.ss_family as i32
    }

    pub fn unix(path: &str) -> Result<Self, Error> {
        let mut un = libc::sockaddr_un {
            sun_family: libc::AF_UNIX as u16,
            sun_path: [0; 108],
        };
        if path.len() > 108 {
            return Err(Error::new(libc::EINVAL));
        }
        unsafe {
            ptr::copy_nonoverlapping(
                path.as_ptr().cast::<libc::c_char>(),
                un.sun_path.as_mut_ptr(),
                path.len(),
            );
        }
        Ok(Self::new(un))
    }

    pub fn inet(ip: &str, port: u16) -> Result<Self, Error> {
        let mut inet = unsafe { MaybeUninit::<libc::sockaddr_in>::zeroed().assume_init_read() };
        inet.sin_family = libc::AF_INET as u16;
        inet.sin_port = unsafe { htons(port) };
        unsafe {
            Self::inet_addr(libc::AF_INET, ip, mut_void(&mut inet.sin_addr))?;
        }

        Ok(Self::new(inet))
    }

    pub fn inet6(ip: &str, port: u16) -> Result<Self, Error> {
        let mut inet = unsafe { MaybeUninit::<libc::sockaddr_in6>::zeroed().assume_init_read() };
        inet.sin6_family = libc::AF_INET6 as u16;
        inet.sin6_port = unsafe { htons(port) };
        unsafe {
            Self::inet_addr(libc::AF_INET6, ip, mut_void(&mut inet.sin6_addr))?;
        }

        Ok(Self::new(inet))
    }

    pub fn get(&self) -> (&libc::sockaddr, libc::socklen_t) {
        let addr = unsafe { self.addr.assume_init_ref() };
        match addr.ss_family as i32 {
            libc::AF_INET => (
                unsafe { &*self.addr.as_ptr().cast::<libc::sockaddr>() },
                mem::size_of::<libc::sockaddr_in>() as libc::socklen_t,
            ),
            libc::AF_INET6 => (
                unsafe { &*self.addr.as_ptr().cast::<libc::sockaddr>() },
                mem::size_of::<libc::sockaddr_in6>() as libc::socklen_t,
            ),
            libc::AF_UNIX => (
                unsafe { &*self.addr.as_ptr().cast::<libc::sockaddr>() },
                mem::size_of::<libc::sockaddr_un>() as libc::socklen_t,
            ),
            _ => panic!("only support AF_INET/AF_INET6/AF_UNIXDOMAIN"),
        }
    }

    pub fn get_mut(&mut self) -> (&mut libc::sockaddr, libc::socklen_t) {
        let addr = unsafe { self.addr.assume_init_ref() };
        match addr.ss_family as i32 {
            libc::AF_INET => (
                unsafe { &mut *self.addr.as_mut_ptr().cast::<libc::sockaddr>() },
                mem::size_of::<libc::sockaddr_in>() as libc::socklen_t,
            ),
            libc::AF_INET6 => (
                unsafe { &mut *self.addr.as_mut_ptr().cast::<libc::sockaddr>() },
                mem::size_of::<libc::sockaddr_in6>() as libc::socklen_t,
            ),
            libc::AF_UNIX => (
                unsafe { &mut *self.addr.as_mut_ptr().cast::<libc::sockaddr>() },
                mem::size_of::<libc::sockaddr_un>() as libc::socklen_t,
            ),
            _ => panic!("only support AF_INET/AF_INET6/AF_UNIXDOMAIN"),
        }
    }

    pub fn get_uninit_mut(&mut self) -> (*mut libc::sockaddr, libc::socklen_t) {
        (self.addr.as_mut_ptr().cast::<libc::sockaddr>(), mem::size_of_val(&self.addr) as libc::socklen_t)
    }

    fn new<T>(val: T) -> Self {
        let mut addr = MaybeUninit::<libc::sockaddr_storage>::uninit();
        unsafe {
            addr.as_mut_ptr().cast::<T>().write(val);
        }
        Self { addr }
    }

    unsafe fn inet_addr(family: i32, src: &str, dst: *mut libc::c_void) -> Result<(), Error> {
        let mut name = [0_u8; 128];
        if src.len() >= 128 {
            return Err(Error::new(libc::EINVAL));
        }
        ptr::copy_nonoverlapping(src.as_ptr(), name.as_mut_ptr(), src.len());

        let ret = inet_pton(family, name.as_ptr(), dst.cast::<u8>());
        if ret == 1 {
            Ok(())
        } else {
            Err(Error::last_error())
        }
    }
}

impl fmt::Debug for SocketAddr {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let addr = unsafe { self.addr.assume_init_ref() };
        match addr.ss_family as i32 {
            libc::AF_INET => {
                let addr = addr as *const _ as *const libc::sockaddr_in;
                let mut buf = [0_u8; 24];
                unsafe { inet_ntop(libc::AF_INET, addr.cast::<u8>(), buf.as_mut_ptr(), 24) };
                let len = unsafe { libc::strlen(buf.as_ptr().cast::<i8>()) };
                let ip = unsafe { str::from_utf8_unchecked(&buf[..len]) };
                let port = unsafe { ntohs((*addr).sin_port) };
                f.write_fmt(format_args!("inet:://{ip}:{port}"))
            }

            libc::AF_INET6 => {
                let addr = addr as *const _ as *const libc::sockaddr_in6;
                let mut buf = [0_u8; 128];
                unsafe { inet_ntop(libc::AF_INET6, addr.cast::<u8>(), buf.as_mut_ptr(), 24) };
                let len = unsafe { libc::strlen(buf.as_ptr().cast::<i8>()) };
                let ip = unsafe { str::from_utf8_unchecked(&buf[..len]) };
                let port = unsafe { ntohs((*addr).sin6_port) };
                f.write_fmt(format_args!("inet6://{ip}:{port}"))
            }
            libc::AF_UNIX => {
                let addr = unsafe { &*(addr as *const _ as *const libc::sockaddr_un) };
                let mut len = addr.sun_path.len();
                for (n, c) in addr.sun_path.iter().enumerate() {
                    if *c == 0 {
                        len = n;
                        break;
                    }
                }
                let path = addr.sun_path[..].as_ptr().cast::<u8>();
                let path = unsafe { slice::from_raw_parts(path, len) };
                let path = unsafe { str::from_utf8_unchecked(path) };
                f.write_fmt(format_args!("unix:://{path}"))
            }
            _ => {
                f.write_fmt(format_args!("unknown family {}", addr.ss_family))
            }
        }
    }
}

impl fmt::Display for SocketAddr {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        fmt::Debug::fmt(self, f)
    }
}
